#include "stream_reassembler.hh"
#include <iostream>

// Dummy implementation of a stream reassembler.

// For Lab 1, please replace with a real implementation that passes the
// automated checks run by `make check_lab1`.

// You will need to add private members to the class declaration in `stream_reassembler.hh`

template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}

using std::cout;
using std::endl;

using namespace std;

#define STM_DEBUG  1
#define _MAGIC 0x1111

StreamReassembler::StreamReassembler(const size_t capacity) :  _cache(capacity + 1, _MAGIC),_output(capacity), _capacity(capacity) {

}

//! \details This function accepts a substring (aka a segment) of bytes,
//! possibly out-of-order, from the logical stream, and assembles any newly
//! contiguous substrings and writes them into the output stream in order.
void StreamReassembler::push_substring(const string &data, const size_t index, const bool eof) {

    // eof ??
    if (eof) {
       eof_index = index + data.length();
    }
    
    // resize cache if read some data
    size_t pop_bytes = _output.bytes_read() - pre_read_size;
    if (pop_bytes > 0) {
       _cache.erase(_cache.begin(), _cache.begin() + pop_bytes);
       _cache.resize(_cache.size() + pop_bytes, _MAGIC);
    }

    size_t first_ur = _output.bytes_read();
    size_t first_ua = _output.bytes_written();
    size_t right_eg = _output.bytes_read() + _capacity;
    size_t asemb_sz = first_ua - first_ur;
    
    #if STM_DEBUG
    printf("\n^stm: index %lu, eof_index %d, bytes_r %lu bytes_w %lu, unacceptable %lu, asembled_sz %lu.\n", \
                  index, eof_index, first_ur, first_ua, right_eg, asemb_sz);
    #endif
    // dup content and also received
    if ((index + data.length()) <= first_ua && !eof) {
	    return;
    }

    // cut valid data to cache
    size_t idx = (index >= _output.bytes_written()) ? 0 :(_output.bytes_written() - index);
    size_t len = (index + data.length()) > right_eg ? (right_eg - index)  : data.length();
    size_t data_redge = (index + data.length()) > right_eg ? right_eg : (index + data.length());
    string new_data = data.substr(idx, len);

    
    // cache data
    size_t data_ledge = data_redge - new_data.length();
    size_t ch_pos = data_ledge >= first_ur ? data_ledge - first_ur : first_ur - first_ur; 
    for (size_t i = 0; i < new_data.length(); i++) {
       _cache[ch_pos + i] = new_data.at(i);
    }
    #if STM_DEBUG
    printf("^stm: idx %lu len %lu, data_ledge %lu, data_reg %lu, ch_pos %lu.\n", idx, len, data_ledge, data_redge, ch_pos);
    #endif

    // traverse and update first unassemble index, also means assemble index
    for (size_t i = asemb_sz; i < _cache.size(); i++) {
       if (_cache[i] == _MAGIC) {
           break;
       }
       
       first_unasem++;
    }

    // calculate unassemble size and flush to bytestream
    size_t unread  = _output.bytes_written() - _output.bytes_read();
    size_t unwrite = first_unasem            - _output.bytes_read();
    #if STM_DEBUG
    printf("^stm: unr %lu unw %lu, asemb_sz %lu, _cache_sz %lu, first_unasemble %lu.\n", unread, unwrite, asemb_sz, _cache.size(), first_unasem);
    #endif
    if (unwrite > unread) {
        std::string str;
        for (size_t i = unread; i < unwrite; ++i) {
            str += static_cast<char>(_cache[i]);
        }  
    #if STM_DEBUG
        printf("^stm: write %lu\n", str.length());
    #endif
        _output.write(str);
    }

    // mark as eof
    if (eof_index != -1 && static_cast<size_t> (eof_index) == _output.bytes_written()) {
        _output.end_input();
    }
    
    // update pre read size, max read was capacity
    pre_read_size = _output.bytes_read();
}

size_t StreamReassembler::unassembled_bytes() const {

    size_t unassemble = 0;
    // data in cache, but not write to bytestream
    // i initial not correct ??
    for (size_t i = _output.bytes_written() - pre_read_size; i < _cache.size(); i++) {
    //for (size_t i = _output.bytes_written() + 1; i < _cache.size(); i++) {
        if (_cache[i] != _MAGIC) {
               unassemble++;
        }
    }

    return unassemble;
}

bool StreamReassembler::empty() const { return _capacity == _output.remaining_capacity(); }
